/*******************************************************************************
 * This file is part of blueprint4j www.blueprint4j.org.
 * 
 *     blueprint4j is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     blueprint4j is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with blueprint4j. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package blueprint4j.gui;

import blueprint4j.Constants;
import blueprint4j.utils.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.io.*;
import java.text.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;

public class JCalendar extends JPanel implements ActionListener {
	final static public int HIGHLIGHT_DAY=1;
	final static public int HOLIDAY_DAY=2;

	final static int UNIT_WIDTH = 22;
	final static int UNIT_HEIGHT = 20;
	final static Dimension SIZE = new Dimension(UNIT_WIDTH*7 + 1, 7*UNIT_HEIGHT + 1);
	static ImageIcon[] images = null;

	private Color color_back = Color.WHITE;
	private Color color_lines = Color.GRAY;
	private Color color_weekend = new Color(255, 254, 179);
	private Color color_days = Color.BLACK;

	private CalendarDayInformationBroker broker=null;

	private SimpleDateFormat format =
		new SimpleDateFormat(
			Settings.getString("gui.calendar.date_format", "MMM yyyy")
		);
	private Date date;
	private JLabel label = new JLabel();
	private AnimatingButton left, right;
	private MonthDisplay month = new MonthDisplay();
	private JCalendarActionListner action_listner=null;

	private int[] day_status=new int[32];

	public JCalendar() {
		this(new Date());
	}

	public JCalendar(Date p_date) {
		super(new BorderLayout());
		setDate(p_date);
		build();
	}

	private void build() {
		try {
			left = new AnimatingButton(null, new String[]{Constants.ICON_BASE+"actions/1leftarrow.png"}, 1);
			right = new AnimatingButton(null, new String[]{Constants.ICON_BASE+"actions/1rightarrow.png"}, 1);
		} catch (IOException ioe) {
			Log.debug.out( ioe);
		}
		formatButton(left);
		formatButton(right);

		JPanel top = new JPanel(new BorderLayout());
		top.add(label, BorderLayout.CENTER);
		label.setHorizontalAlignment(JLabel.CENTER);
		top.add(left, BorderLayout.WEST);
		top.add(right, BorderLayout.EAST);
		add(top, BorderLayout.NORTH);
		add(month, BorderLayout.CENTER);
		setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED));
	}

	public void setDate(Date p_date) {
		date = p_date;
		if (date==null){
			date=new Date();
		}
		label.setText(format.format(date));
		month.repaint();
		if (action_listner!=null){
			action_listner.dateSelected(date);
		}
	}

	public void setActionListner(JCalendarActionListner p_action_listner){
		action_listner=p_action_listner;
	}

	public Date getDate() {
		return date;
	}

	private void formatButton(JButton button) {
		button.setHorizontalAlignment(AbstractButton.CENTER);
		button.setVerticalAlignment(AbstractButton.CENTER);
		button.setHorizontalTextPosition(AbstractButton.CENTER);
		button.setVerticalTextPosition(AbstractButton.CENTER);
		button.setIconTextGap(0);
		button.setMargin(new Insets(0, 0, 0, 0));
		button.setFocusable(false);
		button.addActionListener(this);
		button.setBorder(null);
	}

	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == left) {
			adjustMonths( -1);
		}
		if (e.getSource() == right) {
			adjustMonths(1);
		}
	}

	private void adjustMonths(int by) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		cal.add(Calendar.MONTH, by);
		setDate(cal.getTime());
	}

	private class MonthDisplay extends JComponent implements MouseListener{
		private Font font;
		private Font fontb;
		private int start_day;
		private int days_in_month;
		private int today;
		private Calendar calendar = Calendar.getInstance();

		MonthDisplay() {
			super();
			setMaximumSize(SIZE);
			setMinimumSize(SIZE);
			setPreferredSize(SIZE);
			setSize(SIZE);
			font = new JLabel().getFont().deriveFont(8);
			fontb = font.deriveFont(Font.BOLD);
			addMouseListener(this);
		}

		public void mouseClicked(MouseEvent e) {}

		public void mouseEntered(MouseEvent e) {}

		public void mouseExited(MouseEvent e) {}

		public void mousePressed(MouseEvent e) {
			Point click = e.getPoint();
			// Find date point
			calcDays();
			int day = (int)click.getX() / UNIT_WIDTH + ((int)click.getY() - UNIT_HEIGHT) / UNIT_HEIGHT * 7 - start_day + 1;
			if (day != today && day <= days_in_month) {
				calendar.setTime(getDate());
				calendar.set(Calendar.DAY_OF_MONTH, day);
				setDate(calendar.getTime());
			}
		}

		public void mouseReleased(MouseEvent e) {}

		public void paint(Graphics og) {
			Graphics2D g = (Graphics2D)og;
			if (Settings.getBoolean("gui.calendar.antialias", true)) {
				g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			} else {
				g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
			}

			// Fill
			g.setColor(color_back);
			g.fillRect(0, 0, (int)SIZE.getWidth(), (int)SIZE.getHeight());

			// Draw weekend blocks
			g.setColor(color_weekend);
			g.fillRect(0, UNIT_HEIGHT, UNIT_WIDTH, UNIT_HEIGHT*6);
			g.fillRect(6*UNIT_WIDTH, UNIT_HEIGHT, 7*UNIT_WIDTH, UNIT_HEIGHT*7);

			// Draw lines
			g.setColor(color_lines);
			for (int x = 0;x <= 7;x++) {
				if (x == 0 || x == 7) {
					g.drawLine(x*UNIT_WIDTH, 0, x*UNIT_WIDTH, UNIT_HEIGHT*7);
				} else {
					g.drawLine(x*UNIT_WIDTH, UNIT_HEIGHT, x*UNIT_WIDTH, UNIT_HEIGHT*7);
				}
			}
			for (int y = 0;y <= 7;y++) {
				g.drawLine(0, y*UNIT_HEIGHT, UNIT_WIDTH*7, y*UNIT_HEIGHT);
			}

			// Draw days
			String days = "SMTWTFS";
			g.setColor(color_days);
			for (int d = 0;d < 7;d++) {
				print(g, font, days.substring(d, d + 1), d*UNIT_WIDTH + UNIT_WIDTH / 2, UNIT_HEIGHT / 2);
			}

			// Draw days of month and highlight one
			calcDays();
			// Days of month
			for (int d = 0;d < days_in_month;d++) {
				if ((day_status[d+1]&HIGHLIGHT_DAY)!=0){
					print(
						g, fontb, "" + (d + 1),
						((start_day + d) % 7)*UNIT_WIDTH + UNIT_WIDTH / 2,
						((start_day + d) / 7)*UNIT_HEIGHT + UNIT_HEIGHT / 2 + UNIT_HEIGHT
					);
				} else {
					print(
						g, font, "" + (d + 1),
						((start_day + d) % 7)*UNIT_WIDTH + UNIT_WIDTH / 2,
						((start_day + d) / 7)*UNIT_HEIGHT + UNIT_HEIGHT / 2 + UNIT_HEIGHT
					);
				}
			}
			// Circle
			g.drawImage(
				images[0].getImage(),
				((start_day + today - 1) % 7)*UNIT_WIDTH+2,
				((start_day + today - 1) / 7)*UNIT_HEIGHT + UNIT_HEIGHT+2,
				null
			);

		}

		private void calcDays() {
			calendar.setTime(getDate());
			today = calendar.get(Calendar.DAY_OF_MONTH);
			calendar.set(Calendar.DAY_OF_MONTH, 1);
			start_day = (calendar.get(Calendar.DAY_OF_WEEK) + 6) % 7;
			calendar.add(Calendar.MONTH, 1);
			calendar.add(Calendar.DAY_OF_MONTH, -1);
			days_in_month = calendar.get(Calendar.DAY_OF_MONTH);
		}

		private void print(Graphics2D g, Font font, String string, int x, int y) {
			FontRenderContext frc = g.getFontRenderContext();
			Rectangle2D bounds = font.getStringBounds(string, frc);
			g.setFont(font);
			g.drawString(string, (int)(x - bounds.getWidth() / 2) + 1, (int)(y + bounds.getHeight() / 2) - 2);
		}
	}

	public void setCalendarDayInformationBroker(CalendarDayInformationBroker p_broker){
		broker=p_broker;
		getAllDateStatuses();
	}

	public void getDateStatus(Date p_date){
		Calendar calendar=Calendar.getInstance();
		calendar.setTime(p_date);
		Calendar calendar2=Calendar.getInstance();
		calendar2.setTime(date);
		if (calendar.get(Calendar.YEAR)==calendar2.get(Calendar.YEAR) && calendar.get(Calendar.MONTH)==calendar2.get(Calendar.MONTH)){
			getDateStatus(calendar.get(Calendar.DAY_OF_MONTH));
		}
	}

	public void getAllDateStatuses(){
		Calendar calendar=Calendar.getInstance();
		calendar.setTime(date);
		calendar.set(Calendar.DAY_OF_MONTH,1);
		calendar.add(Calendar.MONTH,1);
		calendar.add(Calendar.DAY_OF_MONTH,-1);
		for (int t=1;t<=calendar.get(Calendar.DAY_OF_MONTH);t++){
			getDateStatus(t);
		}
	}

	private void getDateStatus(int day){
		if (broker!=null) {
			Calendar calendar=Calendar.getInstance();
			calendar.setTime(date);
			calendar.set(Calendar.DAY_OF_MONTH,day);
			day_status[day]=broker.getInfoAboutDay(calendar.getTime());
		} else {
			day_status[day]=0;
		}
	}


	static public Date showDateDialogue(Component parent,String title,Date value){
		JCalendar cal=new JCalendar(value);
		JPanel panel=new JPanel(new FlowLayout());
		panel.add(cal);
		if (JOptionPane.showConfirmDialog(parent,panel,title,JOptionPane.OK_CANCEL_OPTION)==JOptionPane.OK_OPTION){
			return cal.getDate();
		}
		return null;
	}

	static {
		try {
			images = Gui.loadIconsFromResource(new String[]{"blueprint4j/gui/images/ring.png"});
		} catch (IOException ioe) {
			ioe.printStackTrace();
			Log.critical.out( ioe);
			System.exit( -1);
		}
	}
}


